using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WindowsFormsApplication1
{


	public class QrsDet
	{
		private int preBlank;
		private int filtDelay;
		private int derDelay;

		private int minPeakAmp = 7;
		private double th = 0.3125;
		private int[] ddBuffer;
		private static int ddPtr;

		private static int detThresh;
		private static int qpkcnt = 0;
		private static int[] qrsbuf = new int[8];
		private static int[] noise = new int[8];
		private static int[] rrbuf = new int[8];
		private static int[] rsetBuff = new int[8];
		private static int rsetCount = 0;
		private static int nmean, qmean, rrmean;
		private static int count, sbpeak = 0, sbloc;
		private int sbcount = SampleRates.ms1500;
		private static int maxder, lastmax;
		private static int initBlank, initMax;
		private static int preBlankCnt, tempPeak;
		private static int lastDatum;
		private static int max = 0, timeSinceMax = 0;
		private QrsFilt filter;

		public QrsDet ()
		{

		}

		public void resetDetector ()
		{
			for (int i = 0; i < 8; ++i) {
				noise[i] = 0;
				rrbuf[i] = SampleRates.ms1000;
			}

			qpkcnt = maxder = lastmax = count = sbpeak = 0;
			initBlank = initMax = preBlankCnt = ddPtr = 0;
			sbcount = SampleRates.ms1500;
			preBlank = SampleRates.ms195;
			filtDelay = ((SampleRates.derivLength / 2) + (SampleRates.lpBufferLgth / 2 - 1) + ((SampleRates.hpBufferLgth - 1) / 2) + preBlank);
			derDelay = SampleRates.derivLength + filtDelay + SampleRates.derivLength;
			ddBuffer = new int[derDelay];
			filter = new QrsFilt ();
			peak (0, 1);
		}

		public int qrsDet (int datum, int time)
		{
			int fdatum, qrsDelay = 0;
			int newPeak, aPeak;

			//Filtra o dado
		//	fdatum = filter.filter (datum);


			// Wait until normal detector is ready before calling early detections. 
			aPeak = peak (datum, 0);

			if (aPeak < minPeakAmp)
				aPeak = 0;

			// Hold any peak that is detected for ~200 ms
			// in case a bigger one comes along.  There
			// can only be one QRS complex in any ~200 ms window.
			newPeak = 0;
			// If there has been no peak for 200 ms
			if (aPeak != 0 && preBlankCnt == 0) {
				// save this one and start counting.
				tempPeak = aPeak;
				preBlankCnt = preBlank;
				// MS200
			// If we have held onto a peak for
			} else if (aPeak == 0 && preBlankCnt != 0) {
				// 200 ms pass it on for evaluation.
				if (--preBlankCnt == 0)
					newPeak = tempPeak;
			// If we were holding a peak, but
			} else if (aPeak != 0) {
				// this ones bigger, save it and
				// start counting to 200 ms again.
				if (aPeak > tempPeak) {
					tempPeak = aPeak;
					preBlankCnt = preBlank;
					// MS200
				} else if (--preBlankCnt == 0)
					newPeak = tempPeak;
			}

			// Save derivative of raw signal for T-wave and baseline shift discrimination. 

			ddBuffer[ddPtr] = filter.deriv1 (datum, 0);
			if (++ddPtr == derDelay)
				ddPtr = 0;

			// Initialize the qrs peak buffer with the first eight 	
			// local maximum peaks detected.						

			if (qpkcnt < 8) {
				++count;
				if (newPeak > 0) {
					count = SampleRates.windowWidth;
				}

				if (++initBlank == SampleRates.ms1000) {
					initBlank = 0;
					qrsbuf[qpkcnt] = initMax;
					initMax = 0;
					++qpkcnt;
					if (qpkcnt == 8) {
						qmean = mean (qrsbuf, 8);
						nmean = 0;
						rrmean = SampleRates.ms1000;
						sbcount = SampleRates.ms1500 + SampleRates.ms150;
						detThresh = thresh (qmean, nmean);
					}
				}
				if (newPeak > initMax)
					initMax = newPeak;
			// Else test for a qrs. 
			} else {
				++count;
				if (newPeak > 0) {
					/* Check for maximum derivative and matching minima and maxima
                       for T-wave and baseline shift rejection.  Only consider this
                       peak if it doesn't seem to be a base line shift. */

					if (blsCheck (ddBuffer, ddPtr) == 0) {
						// Classify the beat as a QRS complex
						// if the peak is larger than the detection threshold.
						if (newPeak > detThresh) {
							//copia as primeras 7 posicoes de qrsbuf a qrsbuf comecando
							//por 1
							qrsbuf = rotacionaArrayDireita (qrsbuf);
							qrsbuf[0] = newPeak;
							qmean = mean (qrsbuf, 8);
							detThresh = thresh (qmean, nmean);
							rrbuf = rotacionaArrayDireita (rrbuf);
							rrbuf[0] = count - SampleRates.windowWidth;
							rrmean = mean (rrbuf, 8);
							sbcount = rrmean + (rrmean / 2) + SampleRates.windowWidth;
							count = SampleRates.windowWidth;

							sbpeak = 0;

							lastmax = maxder;
							maxder = 0;
							qrsDelay = SampleRates.windowWidth + filtDelay;
							initBlank = initMax = rsetCount = 0;
						// If a peak isn't a QRS update noise buffer and estimate.
						// Store the peak for possible search back.
						} else {
							noise = rotacionaArrayDireita (noise);
							noise[0] = newPeak;
							nmean = mean (noise, 8);
							detThresh = thresh (qmean, nmean);

							// Don't include early peaks (which might be T-waves)
							// in the search back process.  A T-wave can mask
							// a small following QRS.

							if ((newPeak > sbpeak) && ((count - SampleRates.windowWidth) >= SampleRates.ms360)) {
								sbpeak = newPeak;
								sbloc = count - SampleRates.windowWidth;
							}
						}
					}
				}

				// Test for search back condition.  If a QRS is found in
				// search back update the QRS buffer and det_thresh.  

				if ((count > sbcount) && (sbpeak > (detThresh / 2))) {
					qrsbuf = rotacionaArrayDireita (qrsbuf);
					qrsbuf[0] = sbpeak;
					qmean = mean (qrsbuf, 8);
					detThresh = thresh (qmean, nmean);
					rrbuf = rotacionaArrayDireita (rrbuf);
					rrbuf[0] = sbloc;
					rrmean = mean (rrbuf, 8);
					sbcount = rrmean + (rrmean / 2) + SampleRates.windowWidth;
					qrsDelay = count - sbloc;
					count = count - sbloc;
					qrsDelay += filtDelay;
					sbpeak = 0;
					lastmax = maxder;
					maxder = 0;

					initBlank = 0;
					initMax = 0;
					rsetCount = 0;
				}
			}

			// In the background estimate threshold to replace adaptive threshold
			// if eight seconds elapses without a QRS detection.

			if (qpkcnt == 8) {
				if (++initBlank == SampleRates.ms1000) {
					initBlank = 0;
					rsetBuff[rsetCount] = initMax;
					initMax = 0;
					++rsetCount;

					// Reset threshold if it has been 8 seconds without
					// a detection.

					if (rsetCount == 8) {
						for (int i = 0; i < 8; ++i) {
							qrsbuf[i] = rsetBuff[i];
							noise[i] = 0;
						}
						qmean = mean (rsetBuff, 8);
						nmean = 0;
						rrmean = SampleRates.ms1000;
						sbcount = SampleRates.ms1500 + SampleRates.ms150;
						detThresh = thresh (qmean, nmean);
						initBlank = 0;
						initMax = 0;
						rsetCount = 0;
					}
				}
				if (newPeak > initMax) {
					initMax = newPeak;
				}
			}

			return (qrsDelay);
		}


		/***************************************************************************
        * peak() takes a datum as input and returns a peak height
        * when the signal returns to half its peak height, or 
        ****************************************************************************/

		public int peak (int datum, int init)
		{
			int pk = 0;
			//Console.Write (":::: " + max.ToString () + ":::: " + datum.ToString () + "\n");
			if (init != 0)
				max = timeSinceMax = 0;

			if (timeSinceMax > 0)
				++timeSinceMax;

			if ((datum > lastDatum) && (datum > max)) {
				max = datum;
				if (max > 2)
					timeSinceMax = 1;

			} else if (datum < (max / 2)) {
				pk = max;
				max = 0;
				timeSinceMax = 0;

			} else if (timeSinceMax > SampleRates.ms95) {
				pk = max;
				max = 0;
				timeSinceMax = 0;
			}
			lastDatum = datum;

			return (pk);
		}


		/***************************************************************************
        median returns the median of an array of integers.  It uses a slow
        sort algorithm, but these arrays are small, so it hardly matters.
        ****************************************************************************/

		public int median (int[] array, int datnum)
		{
			int i, j, k, temp;
			int[] sort = new int[20];
			for (i = 0; i < datnum; ++i)
				sort[i] = array[i];

			for (i = 0; i < datnum; ++i) {
				temp = sort[i];
				for (j = 0; (temp < sort[j]) && (j < i); ++j) {
				}
				for (k = i - 1; k >= j; --k)
					sort[k + 1] = sort[k];
				sort[j] = temp;
			}
			return (sort[datnum >> 1]);


		}
			/*
        int median(int *array, int datnum)
        {
        long sum ;
        int i ;

        for(i = 0, sum = 0; i < datnum; ++i)
	        sum += array[i] ;
        sum /= datnum ;
        return(sum) ;
        } */


				/****************************************************************************
         thresh() calculates the detection threshold from the qrs median and noise
         median estimates.
        ****************************************************************************/

		public int thresh (int qmedian, int nmedian)
		{
			int thrsh, dmed;
			double temp;
			dmed = qmedian - nmedian;
			//	thrsh = nmedian + (dmed>>2) + (dmed>>3) + (dmed>>4); 
			temp = dmed;
			temp *= th;
			dmed = (int)temp;
			thrsh = nmedian + dmed;
			// dmed * THRESHOLD 
			return (thrsh);
		}


		/****************************************************************************
	        BLSCheck() reviews data to see if a baseline shift has occurred.
	        This is done by looking for both positive and negative slopes of
	        roughly the same magnitude in a 220 ms window.
        *****************************************************************************/

		public int blsCheck (int[] dBuf, int dbPtr)
		{
			int maxb, min, maxt, mint, t, x;
			maxb = min = 0;
			maxt = mint = 0;

			for (t = 0; t < SampleRates.ms220; ++t) {
				x = dBuf[dbPtr];
				if (x > maxb) {
					maxt = t;
					maxb = x;
				} else if (x < min) {
					mint = t;
					min = x;
				}
				if (++dbPtr == derDelay) {
					dbPtr = 0;
				}
			}

			maxder = maxb;
			min = -min;

			/* Possible beat if a maximum and minimum pair are found
           where the interval between them is less than 150 ms. */

			if ((maxb > (min / 8)) && (min > (maxb / 8)) && (Math.Abs (maxt - mint) < SampleRates.ms150)) {
				return (0);
			} else {
				return (0);
			}
		}


		private int mean (int[] array, int datnum)
		{
			long sum;
			int i;

			for (i = 0,sum = 0; i < datnum; ++i) {
				sum += array[i];
			}
			sum /= datnum;

			return ((int)sum);
		}

		private int[] rotacionaArrayDireita (int[] array)
		{
			for (int i = 1; i < 7; i++) {
				array[i] = array[i - 1];
			}
			return (array);
		}
	}

}
